Repository navigation
Add the LambdaTest gateway to incident-scribe and a one-command samples setup - #72
Conversation
…es setup incident-scribe gains a `lambdatest` provider: an OpenAI-compatible chat-completions gateway called with HTTP Basic auth built from LT_USERNAME and LT_ACCESS_KEY, at GATEWAY_URL/v1/chat/completions. The other providers are unchanged, and it is auto-detected last. scripts/check_llm.py reports whether the keys are present, the endpoint is up (GET /healthz) and the credentials are accepted, without printing secrets. Exit codes: 0 ok, 1 not configured, 2 refused, 3 unreachable. samples/setup.sh and setup.ps1 set up every sample with one command: prerequisites, .env files, the incident-scribe token, embeddings and the model check. .env.example now defaults to the LambdaTest gateway. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
samyakLambda
left a comment
There was a problem hiding this comment.
15 inline comments on 5f37d7f. The ones I'd fix before merge:
- check_llm.py exits with code 1 ("not configured") on uncaught errors, so setup gives the wrong hint and still exits 0.
- About 1 in 64 generated tokens starts with
-, and argparse then rejects--token. - One failing
npm run setupaborts setup.sh underpipefail, with no summary. - LambdaTest auto-detection outranks the chat fallback.
.env permissions and the cleartext-http credentials are small security fixes. The rest are edge cases and Windows-specific issues.
| parts = urllib.parse.urlsplit(endpoint) | ||
| url = f"{parts.scheme}://{parts.netloc}/healthz" | ||
| try: | ||
| with urllib.request.urlopen(url, timeout=15) as response: |
There was a problem hiding this comment.
Only ModelError, URLError, TimeoutError and OSError are caught, so any other exception ends in a traceback with Python's default exit code 1. Exit 1 is the "not configured" code, so setup prints the fill-in-your-keys hint, doesn't mark the sample failed, and still ends with "done." and exit 0.
Two ways to hit it:
GATEWAY_URL=rook-samples.lambdatest.com(no scheme):urlopen(':///healthz')raisesValueError: unknown url type.- A
GATEWAY_URLthat answers 200 with an HTML page:json.loadinllm._requestraisesJSONDecodeErrorout ofllm.call.
A final except Exception that prints the error and exits 3 (or a code of its own) would keep these out of the "not configured" path.
There was a problem hiding this comment.
Mostly fixed in ba9646a. The new except Exception in main() covers the model request, but gateway_health() runs before it, and this urlopen still has only the HTTPError and URLError/TimeoutError/OSError handlers. Two GATEWAY_URL typos that pass the https check still escape (tested with plain urllib.request.urlopen):
https://rook-samples..lambdatest.comraisesUnicodeEncodeError(idna: label empty)- a path with a space, e.g.
https://host/llm gateway, raiseshttp.client.InvalidURL
Both still end in a traceback with exit 1, so setup shows the "complete the provider settings" hint, doesn't mark incident-scribe failed, and finishes "done.". test_check_llm_never_reports_a_crash_as_not_configured stubs gateway_health, so it doesn't catch this. An except Exception as error: bad(...) after the two handlers, falling through to return False, would close it.
|
|
||
| if grep -Eq '^SCRIBE_API_TOKEN=[[:space:]]*$' .env; then | ||
| local token | ||
| token="$($PY -c 'import secrets; print(secrets.token_urlsafe(24))')" |
There was a problem hiding this comment.
secrets.token_urlsafe(24) starts with - about 1 time in 64 (1.56% over 200k samples). With such a token, the documented --token "$SCRIBE_API_TOKEN" form fails in argparse with argument --token: expected one argument, so every command fails. preflight.sh doesn't catch it, because it only tries a hard-coded wrong token. secrets.token_hex(24) avoids the problem. Same in setup.ps1 line 71.
| "$PY" scripts/check_llm.py || status=$? | ||
| case "$status" in | ||
| 0) bash scripts/preflight.sh || FAILED="$FAILED incident-scribe" ;; | ||
| 1) |
There was a problem hiding this comment.
This hint is the same whatever provider .env actually configures. Setup keeps an existing .env, so someone with a .env from the old template (LLM_PROVIDER=glm, empty GLM_API_KEY) is told to fill in LT_USERNAME/LT_ACCESS_KEY and that GATEWAY_URL is already set. Adding those keys changes nothing, because LLM_PROVIDER=glm still wins, and every re-run repeats the same advice. Printing the ModelError text from check_llm.py, or the resolved provider, would point to the real gap. setup.ps1 has the same hint.
| else | ||
| red "npm install failed"; FAILED="$FAILED industry-agents"; cd ..; return | ||
| fi | ||
| npm run setup --silent | while IFS= read -r line; do info "$line"; done |
There was a problem hiding this comment.
Under set -euo pipefail, a failing npm run setup fails this pipeline and the script exits right here: no FAILED entry, no "needs attention" summary, and the remaining samples are skipped. If setup-env.mjs throws (a missing .env.example, EACCES), refund-desk, triage-service, knowledge-vault and trip-weather-station never run.
setup.ps1 line 117 has the opposite problem: the failure is dropped and the run ends "done." with exit 0.
Wrapping it, e.g. if ! npm run setup --silent | while IFS= read -r line; do info "$line"; done; then FAILED="$FAILED industry-agents"; fi, would record the failure and carry on.
| requested = "gemini" | ||
| elif os.environ.get("OPENAI_ENDPOINT", "").strip(): | ||
| requested = "responses" | ||
| elif all(os.environ.get(name, "").strip() for name in ("GATEWAY_URL", "LT_USERNAME", "LT_ACCESS_KEY")): |
There was a problem hiding this comment.
The description says LambdaTest is the lowest-priority auto-detect, but this branch comes before the chat fallback. Take a Chat Completions user with OPENAI_API_KEY/OPENAI_CHAT_MODEL, no LLM_PROVIDER line, the GATEWAY_URL line from the new template, and LT_USERNAME/LT_ACCESS_KEY exported for Rook (common in CI). That user used to resolve to chat and now goes to rook-samples.lambdatest.com with their account key, without being told.
Checking for the chat keys first, or auto-selecting lambdatest only when no OpenAI key is set, would match the description. docs/invoking.md lines 88–90 still list the old order, and a precedence test would pin the behaviour.
|
|
||
| cd incident-scribe | ||
| if [ ! -f .env ]; then | ||
| cp .env.example .env |
There was a problem hiding this comment.
cp creates .env with the default umask (usually 0644). This file holds SCRIBE_API_TOKEN and, once filled in, LT_ACCESS_KEY, which signs in to the whole LambdaTest account. industry-agents' setupDemoEnv in shared/config.mjs writes its .env with mode 0o600 for this reason. install -m 600 .env.example .env, or umask 077 before the copy, would match it.
| $envText = Get-Content .env -Raw | ||
| if ($envText -match '(?m)^SCRIBE_API_TOKEN=[ \t]*\r?$') { | ||
| $token = (& $python -c "import secrets; print(secrets.token_urlsafe(24))").Trim() | ||
| $envText = [regex]::Replace($envText, '(?m)^SCRIBE_API_TOKEN=[ \t]*(?=\r?$)', "SCRIBE_API_TOKEN=$token", 1) |
There was a problem hiding this comment.
The static [regex]::Replace has no count overload, so the fourth argument binds to RegexOptions and 1 becomes IgnoreCase. Every matching line is replaced, case-insensitively, instead of only the first as in setup.sh. The instance method does take a count:
$envText = ([regex]'(?m)^SCRIBE_API_TOKEN=[ \t]*(?=\r?$)').Replace($envText, "SCRIBE_API_TOKEN=$token", 1)|
|
||
| def _call_chat(items: list[dict[str, Any]], tools: list[dict[str, Any]] | None, endpoint: str, key: str, model: str, max_tokens: int) -> dict[str, Any]: | ||
| def _call_chat(items: list[dict[str, Any]], tools: list[dict[str, Any]] | None, endpoint: str, key: str, model: str, max_tokens: int, scheme: str = "Bearer") -> dict[str, Any]: | ||
| body: dict[str, Any] = {"model": model, "messages": _chat_items(items), "max_tokens": max_tokens} |
There was a problem hiding this comment.
The lambdatest provider goes through _call_chat, which always sends max_tokens. If LT_MODEL=auto routes to an OpenAI reasoning model (o-series or gpt-5 class) and the gateway passes the body through unchanged, every call fails with a 400 Unsupported parameter: 'max_tokens', because those models expect max_completion_tokens. If the gateway does translate the field, reasoning can use up the 64/1200/1500/2500-token caps and leave the content empty. This lines up with the token-budget item the description lists as unverified, so it's worth confirming what auto resolves to.
There was a problem hiding this comment.
Still open on ba9646a: the lambdatest provider still goes through _call_chat with max_tokens. Whether this is a real problem depends on what LT_MODEL=auto routes to and whether the gateway translates the field. If the gateway owner confirms either one, this can be closed as not applicable.
| Ok "kept existing .env" | ||
| } | ||
|
|
||
| $envText = Get-Content .env -Raw |
There was a problem hiding this comment.
On Windows PowerShell 5.1, Get-Content -Raw without -Encoding reads BOM-less UTF-8 in the ANSI code page, and WriteAllText writes it back as UTF-8, so non-ASCII text is double-encoded. On the first run, line 1 of .env.example ("See README.md → Configuration") becomes README.md → Configuration, and any non-ASCII value in a kept .env is damaged the same way. Get-Content .env -Raw -Encoding UTF8 or [IO.File]::ReadAllText fixes it.
| ) if not value] | ||
| if missing: | ||
| raise ModelError("the LambdaTest gateway is not configured: " + ", ".join(missing)) | ||
| return "lambdatest", _chat_url(endpoint), base64.b64encode(f"{username}:{access_key}".encode()).decode(), model |
There was a problem hiding this comment.
These Basic credentials are the LambdaTest account username and access key, and they go to whatever GATEWAY_URL holds. With an http:// URL (a typo, or a local proxy), the account-wide key is sent in cleartext on every call. Refusing a non-https GATEWAY_URL unless the host is localhost would close that.
Auto-detect precedence, cleartext credentials, HTTP status on ModelError, token generation edge cases, check_llm exit codes and the health probe path. These fail on the previous commit and are fixed in the next one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Check_llm no longer lets an unexpected answer exit 1 (the "not configured" code): errors carry the HTTP status, a bad GATEWAY_URL is caught as configuration, and any other failure exits 3. The gateway must be https (or localhost) because the access key goes with every call, the health probe keeps the URL's path, and auto-detect no longer takes over from an OpenAI chat user. Setup now repairs the token through scripts/ensure_token.py, which follows the loader's first-assignment-wins rule, creates .env as 0600, avoids tokens that start with a dash, passes the chosen Python to preflight, reports a failing `npm run setup`, checks Node 22.13 for the node:sqlite samples, and gives a hint that fits the provider in .env. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
samyakLambda
left a comment
There was a problem hiding this comment.
Follow-up on ba9646a: d3815c4 and ba9646a resolve 14 of the 15 earlier comments. Still open:
gateway_health()can still crash with exit 1 on twoGATEWAY_URLtypos (reply on the earlier exit-code thread).max_tokenswith reasoning models is untouched (reply on that thread).- CI doesn't run the incident-scribe tests.
.github/workflows/test-samples.ymlonly has triage-service and industry-agents jobs, so the seven new tests intest_incident_scribe.pyrun only by hand, and thesetup.sh/setup.ps1changes have no tests. A pytest job forsamples/incident-scribewould keep them running. - A docs nit, inline.
Reviewed by reading the diff; I didn't run the test suite locally.
| Responses, or Chat Completions from the variables that are present, in that | ||
| order. | ||
| Responses, LambdaTest (when `GATEWAY_URL`, `LT_USERNAME` and `LT_ACCESS_KEY` are | ||
| all set and no `OPENAI_API_KEY` is), or Chat Completions from the variables that |
There was a problem hiding this comment.
Nit: the code also skips LambdaTest when OPENAI_INDIA_API_KEY is set (llm.py line 40), so this could say "all set and neither OPENAI_API_KEY nor OPENAI_INDIA_API_KEY is".
scripts/env.sh and env.ps1 load .env into the current shell (source them), with the desk's rules: first assignment wins, the shell wins over the file. setup.sh and setup.ps1 now ask for LT_USERNAME and LT_ACCESS_KEY when they are empty, with the key typed hidden, and print the env line to run next. The docs now cover the loader and the https requirement. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What
1. A
lambdatestprovider for incident-scribe (incident_scribe/llm.py)$GATEWAY_URL/v1/chat/completionswith HTTP Basic auth built fromLT_USERNAMEandLT_ACCESS_KEY.LT_MODELis optional and defaults toauto.LLM_PROVIDER=lambdatest(aliaslt). It is also auto-detected, at the lowest priority, when all three variables are set and noOPENAI_API_KEYis.GATEWAY_URLmust behttps://because the access key is sent with every call.2.
scripts/check_llm.pyChecks that the keys are present, the endpoint is up (unauthenticated
GET /healthz) and the credentials are accepted (one 64-token call). It prints only variable names, never values.Exit codes:
0ok,1not configured,2credentials refused (401/403),3unreachable or request rejected.--config-onlyskips the network.3.
samples/setup.shandsamples/setup.ps1One command sets up every sample, with no flags needed:
node:sqlitesamples).LT_USERNAMEandLT_ACCESS_KEYwhen they are empty (the key is typed hidden), and ends by printing. scripts/env.sh/. .\scripts\env.ps1, which load.envinto your shell.incident-scribe/.envfrom.env.example(mode 600) and generatesSCRIBE_API_TOKENthroughscripts/ensure_token.py, which also repairs an empty, quoted or commented-out token line.check_llm.py, then runs preflight.npm installandnpm run setupfor industry-agents..envis kept.--no-semantic(-NoSemanticon Windows) and an optional list of sample names.4. Docs and config
.env.examplenow defaults to the LambdaTest gateway (GATEWAY_URL=https://rook-samples.lambdatest.com).docs/invoking.md, and added the setup command tosamples/README.md.Review fixes
Addresses the 15 inline review comments, with the failing tests committed first (7 failed before the fix).
check_llm.pyno longer exits 1 ("not configured") on an unexpected answer.ModelErrorcarries the HTTP status: no status is exit 3, 401/403 is exit 2, any other status or anerrorbody is exit 3. A badGATEWAY_URLis exit 1.GATEWAY_URL.npm run setup, and prints a hint that fits the provider already in.env.Behaviour changes to check
setup.sh. That is about a 600 MB download; use--no-semanticto skip it.Testing
/on a fresh copy against a local fake gateway: exit 0, token generated, embeddings installed, checks and preflight passed.check_llm.pywas run against the fake gateway for good credentials, a wrong key (exit 2), a 404 (exit 3), unreachable (exit 3), a missing key (exit 1) and--config-only.https://rook-samples.lambdatest.com/healthzreturns200 {"ok":true}.Not verified
setup.ps1has not been run. There is no PowerShell on the machine it was written on. It mirrorssetup.sh, so please try it on Windows./healthzwas checked live.test_incident_scribe.py::test_index_incident_flywheelfails onmaintoo (it expects semantic mode and gets lexical-only, which needs embeddings installed). It is unrelated to this change.autocould use up that budget before it writes an answer../scripts/preflight.shline fails with "Permission denied", because the file is mode 644 in git.setup.shcalls it withbash. That line is left as it was.🤖 Generated with Claude Code